package com.ler.yrmall.coupon.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ler.yrmall.basic.Form;
import com.ler.yrmall.coupon.dto.CouponDTO;
import com.ler.yrmall.coupon.entity.CouponEntity;
import com.ler.yrmall.coupon.entity.CouponHistoryEntity;
import com.ler.yrmall.coupon.entity.CouponSpuRelationEntity;
import com.ler.yrmall.coupon.form.AvailableCouponForm;
import com.ler.yrmall.coupon.form.CouponForm;
import com.ler.yrmall.coupon.mapper.CouponMapper;
import com.ler.yrmall.coupon.service.CouponHistoryService;
import com.ler.yrmall.coupon.service.CouponService;
import com.ler.yrmall.coupon.service.CouponSpuRelationService;
import com.ler.yrmall.coupon.vo.CouponVO;
import com.ler.yrmall.dto.LoginUser;
import com.ler.yrmall.enums.BasicStatusEnum;
import com.ler.yrmall.enums.ErrorCodeEnum;
import com.ler.yrmall.exception.BusinessException;
import com.ler.yrmall.utils.SecurityUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;


/**
 * @author leron
 */
@Service
public class CouponServiceImpl extends ServiceImpl<CouponMapper, CouponEntity> implements CouponService {

    @Resource
    private CouponHistoryService couponHistoryService;

    @Resource
    private CouponSpuRelationService couponSpuRelationService;

    @Override
    public IPage<CouponVO> find(Form request) {
        return this.baseMapper.find(request.getPage());
    }

    @Override
    public List<CouponEntity> spuCoupon(Long spuId) {
        return this.baseMapper.spuCoupon(spuId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveCoupon(CouponForm request) {
        this.save(request);
        if (CouponEntity.CouponType.PRODUCT.getCode().equals(request.getType())) {
            request.getSpuIds().forEach(item -> {
                CouponSpuRelationEntity cs = new CouponSpuRelationEntity();
                cs.setCouponId(request.getId());
                cs.setSpuId(item);
                cs.insert();
            });
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void receive(long couponId) {
        CouponEntity coupon = this.getById(couponId);
        if (coupon == null) {
            throw new BusinessException("优惠券不存在");
        }
        LocalDateTime today = LocalDateTime.now();
        if (today.isBefore(coupon.getStartTime()) || today.isAfter(coupon.getEndTime())) {
            throw new BusinessException("当前不在开放领取时间内");
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();

        // TODO 分布式锁防抖
        int count = couponHistoryService.getMemberReceiveCount(couponId);
        if (count >= coupon.getLimited()) {
            throw new BusinessException("领取数量已达到上限");
        }

        CouponHistoryEntity ch = new CouponHistoryEntity();
        ch.setCouponId(couponId);
        ch.setMemberId(loginUser.getId());
        ch.setMemberUsername(loginUser.getUsername());
        ch.setReceiveType(CouponEntity.ReceiveType.HAND_RECEIVE.getCode());
        ch.insert();
    }

    @Override
    public CouponVO get(Long id) {
        CouponEntity coupon = this.getById(id);
        CouponVO vo = BeanUtil.copyProperties(coupon, CouponVO.class);
        if (coupon.getType().equals(CouponEntity.CouponType.PRODUCT.getCode())) {
            List<Long> spuIds = couponSpuRelationService.list(Wrappers.<CouponSpuRelationEntity>lambdaQuery()
                            .eq(CouponSpuRelationEntity::getCouponId, id))
                    .stream().map(CouponSpuRelationEntity::getSpuId)
                    .collect(Collectors.toList());
            vo.setSpuIds(spuIds);
        }
        return vo;
    }

    @Override
    public CouponDTO computeBestCoupon(List<Long> spuIdList, BigDecimal amount) {
        List<CouponEntity> list = this.getUserCoupon(amount);
        if (list.isEmpty()) {
            return null;
        }
        // 通用券
        CouponEntity commonAmount = list.stream()
                .filter(item -> item.getType().equals(CouponEntity.CouponType.COMMON.getCode()))
                .max((c1, c2) -> compareAmount(c1.getAmount(), c2.getAmount())).orElse(null);

        // 商品券
        List<Long> collect = list.stream()
                .filter(item -> item.getType().equals(CouponEntity.CouponType.PRODUCT.getCode()))
                .map(CouponEntity::getId).collect(Collectors.toList());
        if (collect.isEmpty()) {
            return BeanUtil.copyProperties(commonAmount, CouponDTO.class);
        }
        List<Long> productCoupon = couponSpuRelationService.list(Wrappers.<CouponSpuRelationEntity>lambdaQuery()
                        .in(CouponSpuRelationEntity::getSpuId, spuIdList)
                        .in(CouponSpuRelationEntity::getCouponId, collect))
                .stream().map(CouponSpuRelationEntity::getCouponId)
                .collect(Collectors.toList());
        if (productCoupon.isEmpty()) {
            return BeanUtil.copyProperties(commonAmount, CouponDTO.class);
        }

        CouponEntity productAmount = list.stream()
                .filter(item -> item.getType().equals(CouponEntity.CouponType.PRODUCT.getCode()))
                .filter(item -> productCoupon.contains(item.getId()))
                .max((c1, c2) -> compareAmount(c1.getAmount(), c2.getAmount())).orElse(null);

        BigDecimal commonDiscount = Optional.ofNullable(commonAmount).map(CouponEntity::getAmount).orElse(BigDecimal.ZERO);
        BigDecimal productDiscount = Optional.ofNullable(productAmount).map(CouponEntity::getAmount).orElse(BigDecimal.ZERO);

        int comparisonResult = commonDiscount.compareTo(productDiscount);

        if (comparisonResult > 0) {
            return BeanUtil.copyProperties(commonAmount, CouponDTO.class);
        }
        return BeanUtil.copyProperties(productAmount, CouponDTO.class);
    }

    @Override
    public List<CouponEntity> getUserCoupon(BigDecimal amount) {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        List<Long> couponIds = couponHistoryService.list(Wrappers.<CouponHistoryEntity>lambdaQuery()
                        .eq(CouponHistoryEntity::getUsed, BasicStatusEnum.SystemEnableStatus.DISABLE.getCode())
                        .eq(CouponHistoryEntity::getMemberId, loginUser.getId())).stream()
                .map(CouponHistoryEntity::getCouponId).collect(Collectors.toList());
        if (couponIds.isEmpty()) {
            return Collections.emptyList();
        }
        LocalDateTime current = LocalDateTime.now();
        List<CouponEntity> list = this.list(Wrappers.<CouponEntity>lambdaQuery()
                .in(CouponEntity::getId, couponIds)
                .le(amount != null,CouponEntity::getMinPoint, amount)
                .le(CouponEntity::getStartTime, current)
                .eq(CouponEntity::getStatus, BasicStatusEnum.SystemEnableStatus.ENABLED.getCode())
                .ge(CouponEntity::getEndTime, current));
        if (list.isEmpty()) {
            return Collections.emptyList();
        }
        return list;
    }

    @Override
    public List<CouponEntity> availableCoupon(AvailableCouponForm request) {
        return this.getUserCoupon(request.getAmount());
    }

    @Override
    public CouponDTO useCoupon(Long couponId, BigDecimal amount) {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        CouponEntity coupon = this.getById(couponId);
        if (!coupon.getStatus().equals(BasicStatusEnum.SystemEnableStatus.ENABLED.getCode())) {
            throw new BusinessException(ErrorCodeEnum.COUPON_DISABLED);
        }
        if (compareAmount(amount, coupon.getAmount()) < 0) {
            throw new BusinessException(ErrorCodeEnum.COUPON_DISABLED);
        }
        List<CouponHistoryEntity> list = this.couponHistoryService.list(Wrappers.<CouponHistoryEntity>lambdaQuery()
                .eq(CouponHistoryEntity::getCouponId, couponId).eq(CouponHistoryEntity::getMemberId, loginUser.getId())
                .eq(CouponHistoryEntity::getUsed, BasicStatusEnum.SystemEnableStatus.DISABLE.getCode()));
        if (list.isEmpty()) {
            throw new BusinessException(ErrorCodeEnum.COUPON_DISABLED);
        }
        CouponHistoryEntity ch = list.get(0);
        ch.setUsed(BasicStatusEnum.SystemEnableStatus.ENABLED.getCode());
        ch.updateById();
        return BeanUtil.copyProperties(coupon, CouponDTO.class);
    }

    @Override
    public Long availableNum() {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        List<Long> couponIds = couponHistoryService.list(Wrappers.<CouponHistoryEntity>lambdaQuery()
                        .eq(CouponHistoryEntity::getUsed, BasicStatusEnum.SystemEnableStatus.DISABLE.getCode())
                        .eq(CouponHistoryEntity::getMemberId, loginUser.getId())).stream()
                .map(CouponHistoryEntity::getCouponId).collect(Collectors.toList());
        if (couponIds.isEmpty()) {
            return 0L;
        }
        LocalDateTime current = LocalDateTime.now();
        return this.count(Wrappers.<CouponEntity>lambdaQuery()
                .in(CouponEntity::getId, couponIds)
                .le(CouponEntity::getStartTime, current)
                .eq(CouponEntity::getStatus, BasicStatusEnum.SystemEnableStatus.ENABLED.getCode())
                .ge(CouponEntity::getEndTime, current));
    }

    private static int compareAmount(BigDecimal amount1, BigDecimal amount2) {
        if (amount1 == null && amount2 == null) {
            return 0;
        } else if (amount1 == null) {
            return -1;
        } else if (amount2 == null) {
            return 1;
        } else {
            return amount1.compareTo(amount2);
        }
    }
}